package simco.framework.gui.services;

import java.util.Date;
import java.util.Map;

import javax.swing.JOptionPane;

import org.osgi.service.event.Event;
import org.osgi.service.event.EventHandler;

import simco.framework.core.ICore;
import simco.framework.gui.graph.ConnectingLine;
import simco.framework.gui.graph.base.GraphObject;
import simco.framework.gui.graph.base.GraphPrimitive;
import simco.framework.gui.services.data.CalendarModel;
import simco.framework.gui.services.data.GraphModel;
import simco.framework.gui.utils.Configuration;
import simco.framework.gui.utils.LocalizationUtil;
import simco.framework.gui.utils.LoggingUtil;
import simco.framework.models.CalendarContent;
import simco.framework.models.CompInfo;
import simco.framework.models.SimulationEvent;

/**
 * Ucelem teto tridy je poslouchat udalosti generovane 
 * simulacni jadrem a na tyto udalosti reagovat.
 * 
 * @author Matej Prokop, mythge@students.zcu.cz
 *
 */
public class CoreEventsListener implements EventHandler
{
	/**
	 * Datovy model calendare udalosti
	 */
	private CalendarModel calendarModel;
	
	/**
	 * Reference na simulacni jadra (resp. rozhrani, ktere
	 * poskytuje)
	 */
	private ICore core;
	
	/**
	 * Objekt ziskavajici informace pro vizualizaci z jadra
	 */
	private CoreInformationProvider infoProvider;
	
    /**
     * Datovy model objektu vykreslovanych na platno 
     */
	private GraphModel graphModel;
	
	/**
	 * Delka historie kalendare udlosti pri poslednim kroku
	 */
	private int lastHistoryLength = Configuration.getHistoryLength();
	
	/**
	 * Cas posledniho prekresleni GUI
	 */
	private long lastUpdateTime = 0;
	
	/**
	 * Minimalni doba prodlevy mezi dve vynucenymi
	 * prekresleni GUI. Pokusy o prekresleni GUI
	 * v kratsim intervalu budou zahozeny.
	 */
	private int MIN_REFRESH_INT = 200;
	
	/**
	 * Nastavuje datovy model kalendare udalosti
	 * @param calendarModel datovy model kalendare udalosti
	 */
	public void setCalendarModel(CalendarModel calendarModel) 
	{
		this.calendarModel = calendarModel;
	}

	/**
	 * Nastavuje simulacni jadro
	 * 
	 * @param core jadro simulace
	 */
	public void setCore(ICore core) 
	{
		this.core = core;
	}
	
	/**
	 * Nastavuje datovy model objektu vykreslovanych na platno 
	 * @param graphModel datovy model objektu vykreslovanych na platno
	 */
	public void setGraphModel(GraphModel graphModel) 
	{
		this.graphModel = graphModel;
	}
	
	/**
	 * Nastavuje objekt poskytujici (ziskavajici) informace z jadra
	 * simulace
	 * 
	 * @param infoProvider reference na objekt poskytujici (ziskavajici) informace z jadra
	 * simulace 
	 */
	public void setInfoProvider(CoreInformationProvider infoProvider) {
		this.infoProvider = infoProvider;
	}

	@Override
	public void handleEvent(Event event) 
	{
		Date d = new Date();
		boolean refresh = false;
		
		synchronized (this) 
		{
			/*
			 * Odflitrovava udalosti prichazejici rychle za sebou,
			 * tak ze zahazuje vsechny udalosti jejichz cas
			 * od posledni udalosti je mensi nez 
			 * konstanta MIN_REFRESH_INT, tim se zamezuje
			 * zahlceni GUI 
			 */
			if(d.getTime() - lastUpdateTime >= MIN_REFRESH_INT)
			{
				refresh = true;
				lastUpdateTime = d.getTime();
			}			
		}
		
		if(refresh)
		{
			simulationStepCompleted();			
		}
	}
	
	/**
	 * Provede aktualizaci vykreslovanych informaci
	 * na platne. Tato metoda je volana po dokonceni
	 * kazdeho kroku tedy pri prichodu udalosti
	 * stepCompleted
	 */
	private void simulationStepCompleted()
	{
		CalendarContent content;
		Map<Long, CompInfo> components;
		
		try
		{			
			content = core.getCalendarContent();
			components = core.getComponents();
		}
		catch (Exception ex) 
		{
			showException(ex);
			return;
		}
		
		// aktualizuje obsah kalendare udalosti
		calendarModel.updateModel(content);		
		
		for(ConnectingLine line : graphModel.getConnLines())
		{
			line.setMarked(false);
		}
		
		for(GraphObject object : graphModel.getGraphObjects() )
		{
			object.setMarked(false);
		}
		
		// oznaci vsechny objekty pouzite v prave dokoncenem kroku
		for(SimulationEvent se : content.getEvents())
		{
			if(se.getTime() != content.getCurrentTime()) 
			{
				continue;
			}
			
			long interId = components.get(se.getTarget()).getIntermediateId();
			if(interId == CompInfo.NO_INTERMEDIATE)
			{
				checkPare(se.getSource(), se.getTarget());
			}
			else
			{
				checkPare(se.getSource(), interId);
				checkPare(interId, se.getTarget());
			}		
						
		}
				 
		// aktualizuje graf
		graphModel.publichChanges();
		
		// necha aktualizovat informace poskytovane infoProviderem 
		infoProvider.refreshInfo();
				
		if(lastHistoryLength != Configuration.getHistoryLength())
		{
			core.setHistoryLength(Configuration.getHistoryLength());
			lastHistoryLength = Configuration.getHistoryLength();
		}
	}
	
	/**
	 * Zvyraznuje vsechny vazby jen lezi na ceste z komponenty
	 * s id source a do komponenty s id target
	 * 
	 * @param source id grafickeho objektu, jenz je zdrojem volani
	 * @param target id grafickeho objektu, jenz je cilem volani
	 */
	private void checkPare(long source, long target)
	{
		for(ConnectingLine line : graphModel.getConnLines())
		{	
			if(line.getFirstEdge().getEdgeOwner().getId() == source && 
					line.getSecondEdge().getEdgeOwner().getId() == 
						graphModel.getObjectById(target + ScenarioLoader.INTERFACE_CONST).getId())
			{
				markConnection(line);
				markConnection(graphModel.getConnMap().get(target + ScenarioLoader.LINE_CONST));				
			}
		}
	}

	/**
	 * Vyznaci vazbu prijatou jako parametr a oba objekty
	 * na jeji dvouch koncich.
	 * 
	 * @param line zvyraznovana vazba
	 */
	private void markConnection(ConnectingLine line)
	{
		((GraphPrimitive) line.getFirstEdge().getEdgeOwner()).setMarked(true);
		((GraphPrimitive) line.getSecondEdge().getEdgeOwner()).setMarked(true);
		line.setMarked(true);
	}
	
	/**
	 * Vypise a zaloguje vyjimku prijatou jako parametr
	 * 
	 * @param ex vypisovana vyjimka
	 */
	private void showException(Exception ex)
	{
		JOptionPane.showMessageDialog(null,
				LocalizationUtil.getGeneralResource().getString("event.coreError") +
				": " + ex.getMessage() );
		LoggingUtil.getGuiLogger().error("Pri ziskavani informaci o aktualnim" +
				" kroku jadro vratilo chybu.", ex);
	}
}
